using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Controls;

namespace be.metanous.mvvm.extensions
{
	/// <summary>
	/// Holds extension methods for <see cref="ICollection{T}"/>.
	/// </summary>
	public static class CollectionExtensions
	{
		/// <summary>
		/// Adds the elements from the specified collection - <paramref name="items"/> to the end of the target <paramref name="collection"/>.
		/// </summary>
		/// <param name="collection">The collection that will be extended.</param>
		/// <param name="items">The items that will be added.</param>
		/// <exception cref="ArgumentNullException"><paramref name="items"/> is null</exception>
		public static void AddRange<T>(this ICollection<T> collection, IEnumerable<T> items)
		{
			if (items == null) throw new ArgumentNullException("items");

			items.ForEach(item => collection.Add(item));
		}

		/// <summary>
		/// Adds the elements from the specified collection - <paramref name="items"/> to the end of the target <paramref name="list"/>.
		/// </summary>
		/// <param name="list">The list that will be extended.</param>
		/// <param name="items">The items that will be added.</param>
		/// <exception cref="ArgumentNullException"><paramref name="items"/> is null</exception>
		public static void AddRange(this IList list, IEnumerable items)
		{
			if (items == null) throw new ArgumentNullException("items");

			foreach (object item in items)
			{
				list.Add(item);
			}
		}

		/// <summary>
		/// Adds the elements from the specified collection - <paramref name="items"/> to the end of the target <paramref name="list"/>.
		/// </summary>
		/// <param name="list">The list that will be extended.</param>
		/// <param name="items">The items that will be added.</param>
		/// <exception cref="ArgumentNullException"><paramref name="items"/> is null</exception>
		public static void AddRange(this ItemCollection list, IEnumerable items)
		{
			if (items == null) throw new ArgumentNullException("items");

			foreach (object item in items)
			{
				list.Add(item);
			}
		}

		/// <summary>
		/// Removes all elements from a collection that match the condition defined by the specified predicate.
		/// </summary>
		/// <typeparam name="T">Elements type.</typeparam>
		/// <param name="collection">The collection, which elements will be removed.</param>
		/// <param name="match">The predicate delegate that defines the condition for the removed elements.</param>
		/// <returns>Number of removed elements.</returns>
		public static int RemoveAll<T>(this ICollection<T> collection, Predicate<T> match)
		{
			var removed =
				(from item in collection
				 where match(item)
				 select item).ToArray();

			foreach (T item in removed)
			{
				collection.Remove(item);
			}

			return removed.Length;
		}

		/// <summary>
		/// Removes all elements from the given collection.
		/// </summary>
		/// <typeparam name="T">Elements type.</typeparam>
		/// <param name="collection">The collection, which will be emptied.</param>
		public static void RemoveAll<T>(this IList<T> collection)
		{
			while (collection.Count != 0)
			{
				collection.RemoveAt(0);
			}
		}

		/// <summary>
		/// Removes all elements from a list that match the condition defined by the specified predicate.
		/// </summary>
		/// <param name="list">The list, which elements will be removed.</param>
		/// <param name="match">The predicate delegate that defines the condition for the removed elements.</param>
		/// <returns>Number of removed elements.</returns>
		public static int RemoveAll(this IList list, Predicate<object> match)
		{
			var removed = new List<object>();
			foreach (object item in list)
			{
				if (match(item))
				{
					removed.Add(item);
				}
			}

			foreach (object item in removed)
			{
				list.Remove(item);
			}

			return removed.Count;
		}

		public static IEnumerable<T> ToEnumerable<T>(this T item)
		{
			yield return item;
		}

		public static IEnumerable<T> TakeBefore<T>(this IEnumerable<T> target, Func<T, bool> predicate)
		{
			if (target.Any(predicate))
			{
				return target.TakeWhile(predicate);
			}

			return Enumerable.Empty<T>();
		}

		public static IEnumerable<T> TakeAfter<T>(this IEnumerable<T> target, Func<T, bool> predicate)
		{
			return target.TakeFrom(predicate).SkipWhile(predicate);
		}

		public static IEnumerable<T> TakeTo<T>(this IEnumerable<T> target, Func<T, bool> predicate)
		{
			if (target.Any(predicate))
			{
				var shouldBreak = false;
				foreach (var item in target)
				{
					if (shouldBreak && !predicate(item))
					{
						break;
					}
					yield return item;
					if (predicate(item))
					{
						shouldBreak = true;
					}
				}
			}
		}

		public static IEnumerable<T> TakeFrom<T>(this IEnumerable<T> target, Func<T, bool> predicate)
		{
			return target.SkipWhile(Reverse(predicate));
		}

		public static IEnumerable<T> TakeBetween<T>(this IEnumerable<T> target, Func<T, bool> predicate1, Func<T, bool> predicate2)
		{
			return target.TakeTo(predicate1).TakeFrom(predicate2).Union(target.TakeTo(predicate2).TakeFrom(predicate1));
		}

		public static IEnumerable<T> TakeTo<T>(this IEnumerable<T> target, T item)
		{
			return target.TakeTo(i => object.Equals(i, item));
		}

		public static IEnumerable<T> TakeFrom<T>(this IEnumerable<T> target, T item)
		{
			return target.TakeFrom(i => object.Equals(i, item));
		}

		public static IEnumerable<T> TakeBetween<T>(this IEnumerable<T> target, T item1, T item2)
		{
			return target.TakeBetween(i => object.Equals(i, item1), i => object.Equals(i, item2));
		}

		public static IEnumerable<T> Append<T>(this IEnumerable<T> source, IEnumerable<T> other)
		{
			foreach (var item in source)
			{
				yield return item;
			}

			foreach (var item in other)
			{
				yield return item;
			}
		}

		public static IEnumerable<T> Append<T>(this IEnumerable<T> source, T other)
		{
			foreach (var item in source)
			{
				yield return item;
			}

			yield return other;
		}

		/// <summary>
		/// Performs the specified action on each element of the collection.
		/// </summary>
		/// <typeparam name="T">Type of the elements in the collection.</typeparam>
		/// <param name="source">The collection on which elements the action will be executed.</param>
		/// <param name="action">The <see cref="Action{T}"/> delegate to perform on each element of the collection.</param>
		internal static void ForEach<T>(this IEnumerable<T> source, Action<T> action)
		{
			foreach (T item in source)
			{
				action(item);
			}
		}

		internal static bool HaveSameElements<T>(this IEnumerable<T> c1, IEnumerable<T> c2)
		{
			var l1 = c1 as IList<T> ?? c1.ToArray();
			var l2 = c2 as IList<T> ?? c2.ToArray();

			if (l1.Count != l2.Count)
				return false;

			for (int i = 0; i < l1.Count; i++)
			{
				if (!l1[i].Equals(l2[i]))
					return false;
			}

			return true;
		}

		internal static bool HaveSameElementsAsSet<T>(this IEnumerable<T> c1, IEnumerable<T> c2)
		{
			return c1.Count() == c2.Count() && c1.All(e1 => c2.Contains(e1));
		}

		private static Func<T, bool> Reverse<T>(Func<T, bool> f)
		{
			return a => !f(a);
		}
	}
}